using System;
using System.IO;
using System.IO.Ports;
using Waymex;
using System.Threading;
using System.Globalization;

namespace Waymex.Gps.Garmin
{
	/// <summary>
	/// Summary description for SerialPort.
	/// </summary>
	internal class SerialLink : IDisposable 
	{
		private ReaderWriterLock objReadWriteLock = new ReaderWriterLock();

        private SerialPort m_commPort = new SerialPort();
        
        private int m_port;
		private int m_baudRate = 9600;

        private bool disposed = false;

		//constants for the receive packet
		private const int DLE = 0x10;
		private const int ETX = 0x03;
		private const byte ACK = 6;
		private const byte NAK = 21;

		//private const int DEFUALT_BAUD_RATE = 9600;
		private const int RX_QUEUE = 1024;

		private const short BUFFER_SIZE = 2048;

		//'create buffer
		private byte[] Buffer = new byte[BUFFER_SIZE];

		private int m_intBufferInPtr;  //points at next free buffer byte
		private int m_intBufferOutPtr; //points at first byte of buffer

        internal SerialLink()
        {
            //m_commPort.DataReceived += new SerialDataReceivedEventHandler(object obj, SerialErrorReceivedEventArgs args);
            m_commPort.DataReceived += new SerialDataReceivedEventHandler(m_commPort_DataReceived);
        }
        internal void Open()
        {
            m_commPort.BaudRate = m_baudRate;
            m_commPort.PortName = String.Concat("COM", m_port.ToString(CultureInfo.InvariantCulture));
            m_commPort.Parity = Parity.None;
            m_commPort.StopBits = StopBits.One;
            m_commPort.Open();

        }
        internal void Close()
        {
            m_commPort.Close();
        }

        public bool Online
        {
            get
            {
                if (m_commPort == null)
                {
                    return false;
                }
                else
                {
                    return m_commPort.IsOpen;
                }
            }
        }

		internal int Port
		{
			get
			{
				return m_port;
			}
			set
			{
				m_port = value;
			}
		}
		internal int BaudRate
		{
			get
			{
				return m_baudRate;
			}
			set
			{
				m_baudRate = value;
			}
		}
		
		private int AddDataToBuffer(byte NewData)
		{
			byte[] bytArray = new byte[1];
			int intBufferSize = 0;

				bytArray[0] = NewData;
				intBufferSize = AddDataToBuffer(bytArray);
	
			return intBufferSize;
		}
        // Public IDisposable.Dispose implementation - calls the internal helper,
        public void Dispose()
        {
            Dispose(true);
        }
        private void Dispose(bool disposing)
        {
            if (!disposed && disposing && m_commPort != null && m_commPort.IsOpen)
            {
                m_commPort.Close();

                // Keep us from calling resetting or closing multiple times
                disposed = true;
            }
        }
        /// <summary>
        /// Converts a string to a byte array.
        /// </summary>
        /// <param name="Data"></param>
        /// <param name="NullTerminate"></param>
        /// <returns></returns>
        private byte[] StringToByte(string Data, bool NullTerminate)
        {
            //TODO: Why is this static
            int iIndex = 0;
            int iLength = 0;
            byte[] bytData = null;

            try
            {
                iLength = Data.Length;

                if (iLength > 0)
                {
                    if (NullTerminate)
                    {
                        bytData = new byte[iLength + 1];

                        for (iIndex = 0; iIndex < (bytData.Length - 1); iIndex++)
                        {
                            //TODO: Remove ref to VB
                            bytData[iIndex] = (byte)Microsoft.VisualBasic.Strings.Asc(Microsoft.VisualBasic.Strings.Mid(Data, iIndex + 1, 1));
                        }

                        //add the null
                        bytData[iLength] = 0;
                    }
                    else
                    {
                        bytData = new byte[iLength];

                        for (iIndex = 0; iIndex < bytData.Length; iIndex++)
                        {
                            bytData[iIndex] = (byte)Microsoft.VisualBasic.Strings.Asc(Microsoft.VisualBasic.Strings.Mid(Data, iIndex + 1, 1));
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                //TraceLog.WriteError(ex)
                throw ex;
            }

            return bytData;

        }
		private int AddDataToBuffer(byte[] NewData)
		{
			//---------------------------------------------------------------------------
			//   DESCRIPTION:    This function adds the bytes in the byte array to the
			//                   cyclic buffer.
			//
			//   PARAMETERS:
			//                   NewData()   An array of bytes
			//
			//   RETURNS:
			//                   <none>
			//
			//---------------------------------------------------------------------------
			
			int intBufferSize = 0;

			try
			{
				
				objReadWriteLock.AcquireWriterLock(-1);

				//increment\roll the byte counter
				for(int f = 0;f <= NewData.GetUpperBound(0);f++)
				{											
					Buffer[m_intBufferInPtr] = NewData[f];
					if(m_intBufferInPtr == BUFFER_SIZE - 1)
					{
						m_intBufferInPtr = 0;
					}
					else
					{
						m_intBufferInPtr = m_intBufferInPtr + 1;
					}
				}

				//calculate the number of bytes in the buffer
				//m_intBufferOutPtr always lags m_intBufferInPtr
				if(m_intBufferInPtr >= m_intBufferOutPtr)
				{
					intBufferSize = m_intBufferInPtr - m_intBufferOutPtr;
				}
				else
				{
					intBufferSize = BUFFER_SIZE - m_intBufferOutPtr + m_intBufferInPtr;
				}

			}
			catch (Exception ex)
			{
				throw ex;
			}
			finally
			{
				objReadWriteLock.ReleaseWriterLock();
			}

			return intBufferSize;

		}

		internal void ClearBuffer()
		{
			//---------------------------------------------------------------------------
			//   DESCRIPTION:    This procedure clears the cyclic buffer.
			//
			//
			//   PARAMETERS:
			//                   <none>
			//
			//   RETURNS:
			//                   <none>
			//
			//---------------------------------------------------------------------------
			
			try
			{
				objReadWriteLock.AcquireWriterLock(-1);
				m_intBufferInPtr = m_intBufferOutPtr;
				objReadWriteLock.ReleaseWriterLock();
			}
			catch(Exception ex)
			{
				throw ex;
			}
		}

        internal void m_commPort_DataReceived(object source, SerialDataReceivedEventArgs eargs)
        {

            int bufferSize = 0;

            //read all available bytes
            int intBytes = m_commPort.BytesToRead;
            byte[] buffer = new byte[intBytes];
            int bytesRead = m_commPort.Read(buffer, 0, intBytes);

            ////get a byte array without care of localisation settings
            //int index = 0;
            //byte[] buffer = new byte[sentence.Length];
            //foreach (char chr in sentence.ToCharArray())
            //{
            //    buffer[index] = (byte)chr;
            //    index++;
            //}

            if (buffer.Length > 0)
                bufferSize = AddDataToBuffer(buffer);
        }

		internal void SendPacket(byte[] bytData)
		{
			try
			{
                m_commPort.Write(bytData,0,bytData.Length);
			}
			catch(Exception ex)
			{
				throw ex;
			}		
		}
		internal byte[] ReceivePacket()
		{
			//---------------------------------------------------------------------------
			//   DESCRIPTION:
			//       This function checks the Module Level byte array 'Buffer' for a
			//       valid packet if a packet is found this is returned as a variant/byte
			//       array and the Buffer is truncated. Any spurious data that occurs
			//       before a discovered packet is discarded.
			//
			//   PARAMETERS:
			//       None
			//
			//   RETURNS:
			//       Variant/Byte Array of bytes
			//       Module Level Buffer Array gets truncated also.
			//
			//---------------------------------------------------------------------------

			byte[]	Pkt = null;
			int		iPktStart = 0;
			int		iPktEnd = 0;
			bool	bDLEFound = false;
			bool	bPktStartFound = false;
			bool	bPktEndFound = false;
			bool	bStuffedDLEFound = false;
			int		iByte = 0;
			int		iPktSize = 0;

			try
			{
				//get a lock
				objReadWriteLock.AcquireReaderLock(-1);
				
				//Debug.WriteLine(String.Concat("Pre:ReceivePacket() Buffer In Pointer ", m_intBufferInPtr.ToString(CultureInfo.InvariantCulture), " Pre:ReceivePacket() Buffer Out Pointer ", m_intBufferOutPtr.ToString(CultureInfo.InvariantCulture)));
				
				iByte = m_intBufferOutPtr;

				//run through buffer looking for a packet
				while ((!(bPktStartFound && bPktEndFound)) && (iByte != m_intBufferInPtr))
				{
					
					System.Windows.Forms.Application.DoEvents();

					//if this is the first DLE found then it could be the start, the end or simply data
					if((Buffer[iByte] == DLE) && (bDLEFound == false))
					{
						//flag the fact that we have discovered a DLE
						bDLEFound = true;
					}
					else if(bDLEFound)
					{
						
						//determine the type of the previous DLE char
						if(Buffer[iByte] == ETX)
							bPktEndFound = true; //DLE folowed by ETX
						if((Buffer[iByte] != ETX) && (Buffer[iByte] != DLE))
							bPktStartFound = true; //DLE followed by non DLE or ETX character
						if(Buffer[iByte] == DLE)
							bStuffedDLEFound = true; //DLE followed by another DLE
						//
						//'ignore all data until a packet start
						if(bPktStartFound)
						{
							//	'start has been found check for an end
							if(bPktEndFound)
							{
								iPktEnd = iByte;
							}
								//	'if not an end then must be either a 'Stuffed' DLE or another start						
							else if(bStuffedDLEFound == false)
							{
								//set the startpointer
								iPktStart = iByte - 1;
								if(iPktStart == -1)
									iPktStart = BUFFER_SIZE - 1;
							}
						}
						else
						{
							//'reset any flags so far as these are not valid until a start is found
							bPktEndFound = false;
							bStuffedDLEFound = false;
						}

						//reset the DLE flag
						bDLEFound = false;

					}					//
					//'increment\roll the byte counter
					if(iByte == BUFFER_SIZE - 1)
					{
						iByte = 0;
					}
					else
					{
						iByte = iByte + 1;
					}
				}

				if(bPktStartFound && bPktEndFound)
				{
					//set the buffer pointer to the new buffer start pos
					m_intBufferOutPtr = iByte;

					//calculate size of packet
					if (iPktEnd > iPktStart)
					{
						iPktSize = iPktEnd - iPktStart + 1;
					}
					else
					{
						iPktSize = (BUFFER_SIZE - iPktStart) + (iPktEnd + 1);
					}
				
					//make room for the found packet
					Pkt = new byte[iPktSize];

					//set the rest of the bytes
					iByte = iPktStart;

					for(int f = 0;f < iPktSize;f++)
					{
						//copy the byte
						Pkt[f] = Buffer[iByte];

						//increment\roll the byte counter
						if (iByte == BUFFER_SIZE - 1)
						{
							iByte = 0;
						}
						else
						{
							iByte = iByte + 1;
						}
					}
				}
			}
			catch(Exception ex)
			{
				throw ex;
			}
			finally
			{
				if(objReadWriteLock.IsReaderLockHeld)
					objReadWriteLock.ReleaseReaderLock();
				if(objReadWriteLock.IsWriterLockHeld)
					objReadWriteLock.ReleaseWriterLock();
			}
			//return the found packet
			return Pkt;
		}

	} //class
} //namespace

